package com.taylor.community.util;

import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

@Component
public class SensitiveFilter {
    private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
    private static final String REPLACEMENT = "***";
    //根节点
    private TrieNode rootNode = new TrieNode();
    @PostConstruct//表示该方法是初始化方法，该类被实例化时会自动调用这个方法
    public void init(){
        try (
                InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");//去编译后的classes目录下找文件
                //缓冲流
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                ){
                String keyword;
                while((keyword = reader.readLine()) != null){
                    this.addKeyword(keyword);
                }
        } catch (Exception e) {
            logger.error("加载敏感词失败："+e.getMessage());
        }
    }
    /**
     * 添加一个敏感词到前缀树
     */
    private void addKeyword(String keyword){
        TrieNode tempNode = rootNode;
        for(int i = 0 ;i<keyword.length();i++){
            char c = keyword.charAt(i);
            //从根节点往下遍历
            TrieNode subNode = tempNode.getSubNode(c);
            if(subNode == null){
                //当前字符还没有存到前缀树
                subNode = new TrieNode();
                tempNode.addSubNode(c,subNode);
            }
            tempNode = subNode;//进入下一轮循环
            //设置循环结束
            if(i == keyword.length() - 1){
                tempNode.setKeywordEnd(true);
            }
        }
    }

    /**
     * 过滤敏感词
     * @param text
     * @return 过滤后的文本
     */
    public String filter(String text){
        if(StringUtils.isBlank(text)){
            return null;
        }
        //指针1 指向根节点
        TrieNode tempNode = rootNode;
        //指针2 指向开始的字符
        int begin = 0;
        //指针3 指向结束的字符
        int position = 0;
        StringBuilder result = new StringBuilder();
        //指针3指向最后一个字符就可以结束
        while(position < text.length()){
            char c = text.charAt(position);
            //跳过符号
            if(isSymbol(c)){
                //指针1处于根节点 而当前字符又是符号 符号不是敏感字符 直接计入结果
                if(tempNode == rootNode){
                    result.append(c);
                    begin++;
                }
                //无论指针3处于哪里 都要往下移动
                position++;
                continue;
            }
            // 比如类似”@敏感@字词@“要过滤掉 但是该写法会连同中间的符号一起去掉
            //检查子节点是否是敏感字符
            tempNode = tempNode.getSubNode(c);
            //不是敏感字符
            if(tempNode == null){
                //以begin开头的不是敏感字符
                result.append(text.charAt(begin));
                position = ++begin;//指针2 3 同时往下移动一位
                tempNode = rootNode;
            }
            else if(tempNode.isKeywordEnd()){
                //加入被替换的字符 相同于替换被视为敏感的字符
                result.append(REPLACEMENT);
                begin = ++position;
                tempNode = rootNode;
            }
            else{
                position++;
            }
        }
        //begin-text.length()-1一直都符合要求 但还不足以构成敏感词 position先到了尾部 循环结束
        //直接将剩余的字符加入到结果
        result.append(text.substring(begin));
        return result.toString();
    }
    private boolean isSymbol(Character c){
        return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
    }
    /**
     * 前缀树，用于过滤敏感词
     */
    private class TrieNode{
        //关键词结束的标志
        private boolean isKeywordEnd = false;
        //子节点（key是子节点的字符 value是子节点）
        private Map<Character,TrieNode> subNodes = new HashMap<>();

        public boolean isKeywordEnd() {
            return isKeywordEnd;
        }

        public void setKeywordEnd(boolean keywordEnd) {
            isKeywordEnd = keywordEnd;
        }
        //添加子节点
        public void addSubNode(Character c,TrieNode node){
            subNodes.put(c,node);
        }
        //获取子节点
        public TrieNode getSubNode(Character c){
            return subNodes.get(c);
        }
    }
}
